﻿using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

namespace Blank.CommonLib.Utils
{
    public class Logger : IDisposable
    {
        #region Dispose

        private bool disposed = false;
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (syncObject != null)
                    {
                        //syncObject.Dispose();
                        syncObject = null;
                    }
                    if (syncParam != null)
                    {
                        syncParam.Dispose();
                        syncParam = null;
                    }
                }

                disposed = true;
            }
        }

        #endregion

        public enum LogLevel : ushort
        {
            Debug = 0,
            Information,
            Warning,
            Error
        }

        private static Logger instance = null;
        public static Logger GetInstance(string fileName = null)
        {
            if (instance == null)
            {
                if (string.IsNullOrEmpty(fileName))
                    instance = new Logger();
                else
                    instance = new Logger(fileName);
            }
            return instance;
        }

        private Logger()
            : this("Run.log")
        {
        }

        private Logger(String fileName)
            : this(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log"), fileName)
        {
        }

        private Logger(String logPathName, String fileName)
        {
            this.StorePathName = logPathName;
            this.FileName = fileName;
        }

        protected readonly static Int32 LogFileMinSize = 1 * 1024 * 1024;           // 最小文件尺寸     1[MByte]
        protected readonly static Int32 LogFileMaxSize = 128 * 1024 * 1024;         // 最大文件尺寸   　128[MByte]
        protected readonly static Int32 BackupFileMaxCount = 1024;                  // 最大备份文件数
        protected readonly static Encoding LogEncode = Encoding.Unicode;            // 日志文件的编码
        protected readonly static String Delimiter = @" ";                          // 日志发生时间，日志级别，日志内容之间的分割符
        protected readonly static String TimeFormat = @"yyyy/MM/dd HH:mm:ss.fff";   // 日志时间的输出格式
        protected readonly static String MessageLevel_Important = @"I";             // 重要级别日志的字符串符号
        protected readonly static String MessageLevel_Error = @"E";                 // 异常级别日志的字符串符号
        protected readonly static String MessageLevel_Warning = @"W";               // 警告级别日志的字符串符号
        protected readonly static String MessageLevel_Information = @"I";           // 信息级别日志的字符串符号
        protected readonly static String MessageLevel_Debug = @"D";                 // Debgu级别日志的字符串符号

        // 进程或者线程对文件写入时的同步用对象
        private Mutex syncObject = null;

        // 线程间对文件读写时的同步用对象
        private ReaderWriterLockSlim syncParam = new ReaderWriterLockSlim();

        private String storePathName = null;
        private String fileName = null;
        private LogLevel outPutLevel = LogLevel.Warning;
        private Int32 maxFileSize = 5 * 1024 * 1024;
        private Int32 backupFileCount = 10;
        private Int32 writeTimeOutMiliSec = 60 * 1000;

        /// <summary>
        /// 日志文件名
        /// </summary>
        public String FileName
        {
            get
            {
                this.syncParam.EnterReadLock();
                try
                {
                    return this.fileName;
                }
                finally
                {
                    this.syncParam.ExitReadLock();
                }
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                {
                    throw new ArgumentException("Log FileName Error.");
                }

                this.syncParam.EnterWriteLock();
                try
                {
                    this.fileName = value;

                    if (this.syncObject != null)
                    {
                        this.syncObject.Close();
                        //this.syncObject.Dispose();
                    }
                    this.syncObject = new Mutex(false, "Global\\" + this.fileName);
                }
                finally
                {
                    this.syncParam.ExitWriteLock();
                }
            }
        }

        /// <summary>
        /// 日志文件名
        /// </summary>
        public String StorePathName
        {
            get
            {
                this.syncParam.EnterReadLock();
                try
                {
                    return this.storePathName;
                }
                finally
                {
                    this.syncParam.ExitReadLock();
                }
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                {
                    throw new ArgumentException("Log StorePathName Error.");
                }

                this.syncParam.EnterWriteLock();
                try
                {
                    this.storePathName = value;
                }
                finally
                {
                    this.syncParam.ExitWriteLock();
                }
            }
        }

        /// <summary>
        /// 日志输出路径（含输出路径）
        /// </summary>
        public String FilePathName
        {
            get
            {
                this.syncParam.EnterReadLock();
                try
                {
                    return Path.Combine(this.storePathName, this.fileName);
                }
                finally
                {
                    this.syncParam.ExitReadLock();
                }
            }
        }

        /// <summary>
        /// 日志输出级别
        /// </summary>
        public LogLevel OutPutLevel
        {
            get
            {
                this.syncParam.EnterReadLock();
                try
                {
                    return this.outPutLevel;
                }
                finally
                {
                    this.syncParam.ExitReadLock();
                }
            }
            set
            {
                this.syncParam.EnterWriteLock();
                try
                {
                    this.outPutLevel = value;
                }
                finally
                {
                    this.syncParam.ExitWriteLock();
                }
            }
        }

        /// <summary>
        /// 保存日志文件的个数
        /// </summary>
        public Int32 BackupFileCount
        {
            get
            {
                this.syncParam.EnterReadLock();
                try
                {
                    return this.backupFileCount;
                }
                finally
                {
                    this.syncParam.ExitReadLock();
                }
            }
            set
            {
                if (value <= 0 || value > BackupFileMaxCount)
                {
                    throw new ArgumentException("Log BackupFileCount Error.");
                }

                this.syncParam.EnterWriteLock();
                try
                {
                    this.backupFileCount = value;
                }
                finally
                {
                    this.syncParam.ExitWriteLock();
                }
            }
        }

        /// <summary>
        /// 日志文件的最大尺寸
        /// </summary>
        public Int32 MaxFileSize
        {
            get
            {
                this.syncParam.EnterReadLock();
                try
                {
                    return this.maxFileSize;
                }
                finally
                {
                    this.syncParam.ExitReadLock();
                }
            }
            set
            {
                if (value < LogFileMinSize || value > LogFileMaxSize)
                {
                    throw new ArgumentException("Log FileSize Error.");
                }

                this.syncParam.EnterWriteLock();
                try
                {
                    this.maxFileSize = value;
                }
                finally
                {
                    this.syncParam.ExitWriteLock();
                }
            }
        }

        /// <summary>
        /// 输出日志文件
        /// 默认输出重要信息级别的日志
        /// OutPutImportantMessage()を使用すること。
        /// </summary>
        /// <param name="message">日志内容</param>
        public void OutPutMessage(String message)
        {
            this.OutPutImportantMessage(message);
        }

        /// <summary>
        /// 输出日志文件
        /// 默认输出重要信息级别的日志
        /// </summary>
        /// <param name="format">输出日志文件的字符串格式</param>
        /// <param name="args">参数</param>
        public void OutPutMessage(String format, params Object[] args)
        {
            //this.OutPutImportantMessage(format, null, args);
            this.OutPutImportantMessage(format, args);
        }

        /// <summary>
        /// 输出重要信息级别的日志
        /// </summary>
        /// <param name="message">日志内容</param>
        public void OutPutImportantMessage(String message)
        {
            this.OutPutImportantMessage("{0}", message);
        }

        /// <summary>
        /// 输出重要信息级别的日志
        /// </summary>
        /// <param name="format">输出日志文件的字符串格式</param>
        /// <param name="args">参数</param>
        public void OutPutImportantMessage(String format, params Object[] args)
        {
            this.OutputString(MessageLevel_Important, format, null, args);
        }

        /// <summary>
        /// 输出Debug级别的日志
        /// </summary>
        /// <param name="message">日志内容</param>
        public void OutPutDebugMessage(String message)
        {
            this.OutPutDebugMessage("{0}", message);
        }

        /// <summary>
        /// 输出Debug级别的日志
        /// </summary>
        /// <param name="format">输出日志文件的字符串格式</param>
        /// <param name="args">参数</param>
        public void OutPutDebugMessage(String format, params Object[] args)
        {
            if (this.OutPutLevel <= LogLevel.Debug)
            {
                this.OutputString(MessageLevel_Debug, format, null, args);
            }
        }

        /// <summary>
        /// 输出信息级别的日志
        /// </summary>
        /// <param name="message">日志内容</param>
        public void OutPutInfomationMessage(String message)
        {
            this.OutPutInfomationMessage("{0}", message);
        }

        /// <summary>
        /// 输出信息级别的日志
        /// </summary>
        /// <param name="format">输出日志文件的字符串格式</param>
        /// <param name="args">参数</param>
        public void OutPutInfomationMessage(String format, params Object[] args)
        {
            if (this.OutPutLevel <= LogLevel.Information)
            {
                this.OutputString(MessageLevel_Information, format, null, args);
            }
        }

        /// <summary>
        /// 输出警告级别的日志
        /// </summary>
        /// <param name="message">日志内容</param>
        public void OutPutWarningMessage(String message)
        {
            this.OutPutWarningMessage("{0}", null, message);
        }

        /// <summary>
        /// 输出警告级别的日志
        /// </summary>
        /// <param name="format">输出日志文件的字符串格式</param>
        /// <param name="args">参数</param>
        public void OutPutWarningMessage(String format, params Object[] args)
        {
            this.OutPutWarningMessage(format, null, args);
        }

        /// <summary>
        /// 输出异常级别的日志
        /// </summary>
        /// <param name="message">日志内容</param>
        public void OutPutErrorMessage(String message)
        {
            this.OutPutErrorMessage("{0}", null, message);
        }

        public void OutPutException(Exception ex)
        {
            this.OutPutErrorMessage("程序发生异常。内容：{0}\r\n位置：{1}", ex.Message, ex.StackTrace);
        }

        /// <summary>
        /// 输出异常级别的日志
        /// </summary>
        /// <param name="format">输出日志文件的字符串格式</param>
        /// <param name="args">参数</param>
        public void OutPutErrorMessage(String format, params Object[] args)
        {
            this.OutPutErrorMessage(format, null, args);
        }

        /// <summary>
        /// 输出异常级别的日志
        /// </summary>
        /// <param name="message">日志内容</param>
        /// <param name="e">发生的异常的Exception对象</param>
        public void OutPutErrorMessage(String message, Exception e)
        {
            this.OutPutErrorMessage("{0}", e, message);
        }

        /// <summary>
        /// エラーログを出力する
        /// </summary>
        /// <param name="format">メッセージのフォーマット</param>
        /// <param name="e">発生した例外（メッセージとスタックトレースを出力）</param>
        /// <param name="args">出力するオブジェクト配列</param>
        public void OutPutErrorMessage(String format, Exception e, params Object[] args)
        {
            //if (this.OutPutLevel <= LogLevel.Error)
            //{
            this.OutputString(MessageLevel_Error, format, e, args);
            //}
        }

        /// <summary>
        /// 输出日志用内部函数
        /// </summary>
        /// <param name="messageType">日志级别</param>
        /// <param name="format">输出字符串格式</param>
        /// <param name="e">发生异常的Exception对象</param>
        /// <param name="args">参数</param>
        protected virtual void OutputString(String messageType, String format, Exception e, params Object[] args)
        {
            try
            {
                this.OutputString(messageType, String.Format(format, args), e);
            }
            catch (Exception formatException)
            {
                String message;

                message = String.Concat("<--- Log Message Format Error --->", " Format = < ", format, " >");
                this.OutputString(MessageLevel_Error, message, formatException);

                message = String.Concat("Error Message Args = < [ ", String.Join(" ][ ", args.Cast<string>().ToArray()), " ] >");
                //message = String.Concat("Error Message Args = < [ ", String.Join(" ][ ", args), " ] >");
                this.OutputString(MessageLevel_Error, message, null);
            }
        }

        /// <summary>
        /// 输出日志用内部函数
        /// </summary>
        /// <param name="messageType"></param>
        /// <param name="message"></param>
        /// <param name="e"></param>
        protected virtual void OutputString(String messageType, String message, Exception e)
        {
            // 进程间排他处理
            if (!this.EnterWriteLock(this.writeTimeOutMiliSec))
            {
                return;
            }

            DateTime now = DateTime.Now;

            try
            {
                String pathName = this.StorePathName;
                String fileName = this.FilePathName;
                Int32 maxSize = this.MaxFileSize;
                Int32 fileCount = this.BackupFileCount;

                if (!Directory.Exists(pathName))
                {
                    Directory.CreateDirectory(pathName);
                }

                Int64 fileSize = WriteMessage(fileName, now, messageType, message, e);
                if (fileSize > maxSize)
                {
                    //Stopwatch aa = new Stopwatch();

                    // 复数个日志文件重新排序命名
                    BackupFiles(pathName, fileName, fileCount, now);
                    //aa.Stop();
                    //Console.WriteLine(aa.ElapsedMilliseconds);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                // 即使发生例外也不抛出异常
            }
            finally
            {
                this.ExitWriteLock();
            }
        }

        /// <summary>
        /// 输出文件
        /// </summary>
        /// <param name="logFileName"></param>
        /// <param name="logTime"></param>
        /// <param name="messageType"></param>
        /// <param name="message"></param>
        /// <param name="e"></param>
        /// <returns>文件尺寸</returns>
        private static Int64 WriteMessage(String logFileName, DateTime logTime, String messageType, String message, Exception e)
        {
            FileStream stream = null;
            Int64 fileSize = 0;
            try
            {
                stream = File.Open(logFileName, FileMode.Append, FileAccess.Write, FileShare.None);
                using (StreamWriter writer = new StreamWriter(stream, LogEncode))
                {
                    stream = null;

                    // 写入日志时间
                    writer.Write(logTime.ToString(TimeFormat));
                    // 分隔符
                    writer.Write(Delimiter);
                    // 写入日志级别符合
                    writer.Write("["); writer.Write(messageType); writer.Write("]");
                    // 分隔符
                    writer.Write(Delimiter);
                    // 写入日志内容
                    writer.WriteLine(message);
                    // 写入异常信息
                    if (e != null)
                    {
                        writer.Write("Message : "); writer.WriteLine(e.Message);

                        writer.Write("Source : "); writer.WriteLine(e.Source);

                        writer.Write("StackTrace : "); writer.WriteLine(e.StackTrace);
                    }

                    // 导出文件
                    writer.Flush();

                    fileSize = writer.BaseStream.Length;

                    writer.Close();
                }
            }
            finally
            {
                if (stream != null)
                {
                    stream.Dispose();
                }
            }

            return fileSize;
        }

        /// <summary>
        /// 备份日志文件
        /// </summary>
        /// <param name="storePathName"></param>
        /// <param name="fileName"></param>
        /// <param name="fileMaxCount"></param>
        /// <param name="logTime"></param>
        private static void BackupFiles(String storePathName, String fileName, Int32 fileMaxCount, DateTime logTime)
        {
            String fileTitle = Path.GetFileNameWithoutExtension(fileName);
            String fileExt = Path.GetExtension(fileName);

            // 备份日志文件
            // 备份文件夹目标路径
            String destination = MakeBackupFileName(storePathName, fileTitle, fileExt, logTime);
            if (File.Exists(destination))
            {
                // 如果备份目标路径同日志输出路径则不作处理
                return;
            }

            // bei
            String source = Path.Combine(storePathName, fileName);
            if (!File.Exists(source))
            {
                // 源文件不存在时不作处理
                return;
            }

            // 备份文件（变更文件名）
            try
            {
                File.Move(source, destination);
            }
            catch
            {
                // 移動に失敗しても削除処理を行うようにするため、例外を無視する
            }

            // バックアップファイルの削除
            String searchFileName = MakeBackupFileNamePattern(fileTitle, fileExt);
            DeleteOldFiles(storePathName, searchFileName, fileMaxCount);
        }

        /// <summary>
        /// バックアップファイルの削除
        /// </summary>
        /// <param name="storePathName"></param>
        /// <param name="searchFileName"></param>
        /// <param name="fileMaxCount"></param>
        private static void DeleteOldFiles(String storePathName, String searchFileName, Int32 fileMaxCount)
        {
            // バックアップされたログファイル名のリストを取得
            String[] logFileNames = Directory.GetFiles(storePathName, searchFileName, SearchOption.TopDirectoryOnly);
            if (logFileNames.Length <= fileMaxCount)
            {
                return;
            }

            // 古いファイルを削除
            Array.Sort(logFileNames);
            Int32 deleteCount = logFileNames.Length - fileMaxCount;
            for (Int32 i = 0; i < deleteCount; i++)
            {
                try
                {
                    File.Delete(logFileNames[i]);
                }
                catch
                {
                    // 一度削除できないファイルができると、バックアップファイルが増え続ける
                    // 例外発生時には次のファイルを削除するように例外を無視する。
                }
            }
        }

        /// <summary>
        /// バックアップファイル名の作成
        /// </summary>
        /// <param name="storePathName"></param>
        /// <param name="fileTitle"></param>
        /// <param name="fileExt"></param>
        /// <param name="backupTime"></param>
        /// <returns></returns>
        private static String MakeBackupFileName(String storePathName, String fileTitle, String fileExt, DateTime backupTime)
        {
            return Path.Combine(storePathName, String.Format("{0}{1}{2}", fileTitle, backupTime.ToString("_yyyyMMdd_HHmmss"), fileExt));
        }

        /// <summary>
        /// バックアップファイルの検索パターン文字列の作成
        /// </summary>
        /// <param name="fileTitle"></param>
        /// <param name="fileExt"></param>
        /// <returns></returns>
        private static String MakeBackupFileNamePattern(String fileTitle, String fileExt)
        {
            return String.Format("{0}_????????_??????{1}", fileTitle, fileExt);
        }

        /// <summary>
        /// メッセージレベルに対する出力時の文字列を取得する
        /// </summary>
        /// <param name="messageType">ログメッセージレベル</param>
        /// <returns></returns>
        protected static String GetMessageLevelString(LogLevel messageType)
        {
            String result;

            switch (messageType)
            {
                case LogLevel.Information:
                    result = MessageLevel_Information;
                    break;
                case LogLevel.Warning:
                    result = MessageLevel_Warning;
                    break;
                case LogLevel.Error:
                    result = MessageLevel_Error;
                    break;
                case LogLevel.Debug:
                    result = MessageLevel_Debug;
                    break;
                default:
                    result = MessageLevel_Error;
                    break;
            }

            return result;
        }

        /// <summary>
        /// 書き込み用のロック
        /// </summary>
        /// <param name="timeOutMilliSecond"></param>
        /// <returns></returns>
        protected Boolean EnterWriteLock(Int32 timeOutMilliSecond)
        {
            Boolean result = false;

            try
            {
                result = this.syncObject.WaitOne(timeOutMilliSecond);
            }
            catch (AbandonedMutexException)
            {
                // 強制終了で破棄されていた場合は、続行 
                result = true;
            }
            catch (Exception)
            {
                // 例外発生時でも、例外をスローしない
                result = false;
            }

            return result;
        }

        /// <summary>
        /// 書き込み用のアンロック
        /// </summary>
        protected void ExitWriteLock()
        {
            try
            {
                // 書き込み終了
                this.syncObject.ReleaseMutex();
            }
            catch (Exception)
            {
                // 強制終了で破棄されていた場合は、続行
            }
        }
    }
}
